home *** CD-ROM | disk | FTP | other *** search
/ PCMania 73 / PCMania CD73_1.iso / sharewar / utiles / viff / viffedit.c < prev    next >
C/C++ Source or Header  |  1998-02-18  |  29KB  |  1,200 lines

  1. /************************* -*- Mode: C -*- *****************************
  2.  *
  3.  * viffedit.c -- emacs commands for viff edit mode
  4.  *
  5.  * Copyright (C) 1995-1998 Richard Flamsholt S0rensen.  All rights reserved.
  6.  *
  7.  * Author          : Richard Flamsholt S0rensen
  8.  * Created On      : Tue Oct 24 22:57:36 1995
  9.  * Last Modified By: Richard Flamsholt S0rensen
  10.  * Last Modified On: Wed Feb 18 14:15:40 1998
  11.  * Update Count    : 200
  12.  * Revision History: None
  13.  *
  14.  * COMMENTS
  15.  * HISTORY
  16.  **********************************************************************/
  17.  
  18. #include <stdio.h>
  19. #include <stdlib.h>
  20. #include <string.h>
  21. #include <stdarg.h>
  22. #include <ctype.h>
  23. #include <limits.h>
  24. #include "viff.h"
  25.  
  26. #define LINETABNLINC        20
  27. #define ROOMINC            30
  28. #define KILLINC            200
  29. #define MAX_SEARCH_LEN        50
  30. #define SEARCH_RESULT_CACHE    30
  31.  
  32. typedef struct {
  33.   char *string;
  34.   char flags;
  35.   int len;
  36.   long match;
  37. } SEARCH_RESULT;
  38.  
  39. static char *editmode_help[] = {
  40.   "        Viff editor",
  41.   "",
  42.   "kill to end:        C-k",
  43.   "open up a line:     C-o",
  44.   "search forward:     C-s",
  45.   "search backward:    C-r",
  46.   "insert killed text: C-y",
  47.   "goto start of text: C-x <",
  48.   "goto end of text:   C-x >",
  49.   "goto a line:        C-x C-l",
  50.   "zap trailing space: C-x C-z",
  51.   "",
  52.   "exit to local diff: C-l",
  53.   "exit editmode:  esc ins",
  54.   ""
  55. };
  56. static char *searchmode_help[] = {
  57.   "                  Viff search",
  58.   "",
  59.   "forward search:  C-s   case sensitivity: C-c",
  60.   "backward search: C-r   match whole word: C-w",
  61.   "",
  62.   "stop search and go back to current diff: C-g",
  63.   "stop search and go to local diff:        C-l",
  64.   "stop search and enter editmode:      esc ins",
  65.   "",
  66.   "toggle between text and search string:   tab",
  67.   "",
  68.   "any other key:",
  69.   "  in text; exit search and enter editmode",
  70.   "  in search; edit the search string",
  71.   ""
  72. };
  73. static char *gotoline_help[] = {
  74.   "To goto a specific line you need",
  75.   "to tell whether you wish to go to",
  76.   "a line in file1 or file2.",
  77.   "",
  78.   " goto a line in file1: 1",
  79.   " goto a line in file2: 2"
  80. };
  81.  
  82. static int Memx, Winx, Winy;
  83. static int StickyWinx;
  84. static LINE *Winl;
  85.  
  86. static char *kill_text;
  87. static int kill_len;
  88. static int kill_room;
  89. static BOOLEAN kill_append;
  90. static BOOLEAN editmode = FALSE;
  91.  
  92. static void cleanup_edit_mode(void);
  93. static void ctrl_x_command(void);
  94. static void zap_trailing_whitespace(void);
  95. static BOOLEAN search_forward(int line, int pos, char *str, int len,
  96.                               BOOLEAN fold, BOOLEAN wholeword);
  97. static BOOLEAN search_backward(int line, int pos, char *str, int len,
  98.                                BOOLEAN fold, BOOLEAN wholeword);
  99. static void edit_goto_line(void);
  100. static void edit_goto_pos(int line, int memx);
  101. static void edit_focus_line(int line);
  102. static void set_Winl(void);
  103. static void set_Memx(int memx);
  104. static void set_valid_Memx(void);
  105. static void set_Scrollx(void);
  106. static void enlarge_line(LINE *line, int len);
  107. static void update_killed_text(char *add, int len);
  108. static void edit_top(void);
  109. static void edit_bottom(void);
  110. static void edit_home(void);
  111. static void edit_end(void);
  112. static BOOLEAN edit_delete(void);
  113. static BOOLEAN edit_up(void);
  114. static BOOLEAN edit_down(void);
  115. static BOOLEAN edit_left(void);
  116. static BOOLEAN edit_right(void);
  117. static BOOLEAN edit_insert_ch(int ch);
  118. static BOOLEAN edit_insert_str(char *text);
  119.  
  120.  
  121. void
  122. init_edit_mode(void)
  123. {
  124.   kill_text = NULL;
  125.   atexit(cleanup_edit_mode);
  126. }
  127.  
  128.  
  129. static void
  130. cleanup_edit_mode(void)
  131. {
  132.   free(kill_text);
  133. }
  134.  
  135.  
  136. void
  137. edit_mode(void)
  138. {
  139.   int ch;
  140.   BOOLEAN append = FALSE;
  141.  
  142.   status_attr = edit_attr;
  143.   if (!editmode) {
  144.     editmode = TRUE;
  145.     getyx(stdscr, Winy, Winx/*unused*/);
  146.     StickyWinx = Winx = Memx = 0;
  147.     Scrollx = 0;
  148.     set_Winl();
  149.  
  150.     if (Winy < VIEWLINES && topln+Winy == diffmid) { /* now viewing curr diff */
  151.       if (linetbl[diffbeg].incl) {
  152.     edit_up();
  153.       } else {
  154.     edit_down();
  155.       }
  156.     }
  157.     if (Winy < SCROLLSIGHT) {
  158.       Winy = MIN(nline-1, SCROLLSIGHT);
  159.       set_Winl();
  160.     } else if (VIEWLINES-SCROLLSIGHT < Winy) {
  161.       Winy = VIEWLINES-SCROLLSIGHT;
  162.       set_Winl();
  163.     }
  164.   }
  165.  
  166.   for (;;) {
  167.     edit_statusline(Winl, Memx, Winx);
  168.     (void)move(Winy, Winx-Scrollx);
  169.     (void)refresh();
  170.     ch = getch();
  171.  
  172.     kill_append = append;
  173.     append = FALSE;
  174.  
  175.     switch (ch) {
  176.     case KEY_ESCAPE:
  177.     case KEY_IC:
  178.     case KEY_EIC:
  179.       status_attr = viff_attr;
  180.       edit_home();
  181.       goto_curr_diff(FALSE);
  182.       editmode = FALSE;
  183.       return;
  184.     case KEYCTRL('L'):
  185.       status_attr = viff_attr;
  186.       edit_home();
  187.       goto_nearest_diff(topln+Winy);
  188.       editmode = FALSE;
  189.       return;
  190. #ifdef KEY_SHELP
  191.     case KEY_SHELP:
  192. #endif
  193. #ifdef KEY_LHELP
  194.     case KEY_LHELP:
  195. #endif
  196.     case KEY_F(1):
  197.       help(editmode_help, ARRAYSIZE(editmode_help));
  198.       break;
  199.     case KEY_BACKSPACE:
  200.     case '\x08':
  201.       /* Allow deleting an empty line by using backspace even if the
  202.        * line above is a system line.  In that special case we delete
  203.        * the line using delete() and move up onto the line above.
  204.        */
  205.       if (Winy > 0 && SYSLINE(Winl-1) && !SYSLINE(Winl) && Winl->len == 0) {
  206.     if (edit_delete()) edit_up();
  207.       } else {
  208.     if (edit_left()) edit_delete();
  209.       }
  210.       break;
  211.     case KEY_DELETE:
  212.     case KEY_DC:
  213.     case KEYCTRL('D'): edit_delete(); break;
  214.     case KEY_UP:
  215.     case KEYCTRL('P'): edit_up(); break;
  216.     case KEY_DOWN:
  217.     case KEYCTRL('N'): edit_down(); break;
  218.     case KEY_LEFT:
  219.     case KEYCTRL('B'): edit_left(); break;
  220.     case KEY_RIGHT:
  221.     case KEYCTRL('F'): edit_right(); break;
  222.     case KEY_HOME:
  223.     case KEYCTRL('A'): edit_home(); break;
  224.     case KEY_END:
  225.     case KEYCTRL('E'): edit_end(); break;
  226.     case KEYCTRL('Y'): edit_insert_str(kill_text); break;
  227.     case KEYCTRL('O'):
  228.       { int scrollx = Scrollx;
  229.     if (edit_insert_ch('\n')) {
  230.       edit_left();
  231.       Scrollx = scrollx;
  232.       putline(topln+Winy);
  233.       move(Winy, Winx-Scrollx);
  234.     }
  235.       }
  236.       break;
  237. #ifdef KEY_EOL
  238.     case KEY_EOL:
  239. #endif
  240.     case KEYCTRL('K'):
  241.       if (SYSLINE(Winl)) break;
  242.       if (Memx == Winl->len) {          /* at end of line */
  243.     update_killed_text("\n", 1);
  244.     edit_delete();
  245.       } else {
  246.     update_killed_text(Winl->txt+Memx, Winl->len-Memx);
  247.     Winl->len = Memx;
  248.     putline(topln+Winy);
  249.       }
  250.       append = TRUE;
  251.       break;
  252.     case KEYCTRL('S'):
  253.     case KEYCTRL('R'):
  254.       search(ch);
  255.       break;
  256.     case KEY_PPAGE: goto_prev_page(); break;
  257.     case KEY_NPAGE:
  258.     case KEYCTRL('V'): goto_next_page(); break;
  259.     case KEYCTRL('X'):
  260.       ctrl_x_command();
  261.       break;
  262.     default:
  263.       edit_insert_ch(ch);
  264.       break;
  265.     }
  266.   }
  267. }
  268.  
  269.  
  270. static void
  271. ctrl_x_command(void)
  272. {
  273.   int ch;
  274.  
  275.   statusline(0, "C-x");
  276.   (void)move(VIEWLINES, 5);
  277.   (void)refresh();
  278.   ch = getch();
  279.   if (KEYABORT(ch)) return;
  280.   switch (ch) {
  281.   case '<':
  282.     edit_top();
  283.     break;
  284.   case '>':
  285.     edit_bottom();
  286.     break;
  287.   case KEYCTRL('L'):
  288.     edit_goto_line();
  289.     break;
  290.   case KEYCTRL('Z'):
  291.     zap_trailing_whitespace();
  292.     break;
  293.   default:
  294.     (void)beep();
  295.   }
  296. }
  297.  
  298.  
  299. static void
  300. zap_trailing_whitespace(void)
  301. {
  302.   LINE *winl;
  303.   unsigned long zap;
  304.   int i;
  305.  
  306.   zap = 0;
  307.   for (i=0, winl=linetbl; i < nline; i++, winl++) {
  308.     if (SYSLINE(winl)) continue;
  309.     while (winl->len > 0 && isspace(((unsigned char*)winl->txt)[winl->len-1])) {
  310.       winl->len--;
  311.       zap++;
  312.     }
  313.   }
  314.   set_valid_Memx();
  315.   for (i = 0; i < VIEWLINES; i++) {
  316.     putline(topln+i);
  317.   }
  318.   if (zap > 0) modified = TRUE;
  319.   message("%lu space%s zapped", zap, zap==1?"":"s");
  320. }
  321.  
  322.  
  323. /**********************************************************************/
  324.  
  325. /* To speed up the search as much as possible I have taken out the
  326.    foldcase/wholeword test from the inner loops and made four tight
  327.    loops for each foldcase/wholeword combination instead.  This macro
  328.    embodies the code and run 20-50% faster.
  329.  */
  330. #define SEARCH_DO(wwif1,wwif2,foldfunc,inputbuffer)    \
  331.   { int i, j, count;                    \
  332.     LINE *winl;                        \
  333.     char *p;                        \
  334.     for (i=0, winl=linetbl; i < nline; i++, winl++) {    \
  335.       if (SYSLINE(winl)) continue;            \
  336.       count = winl->len-len;                \
  337.       for (j=0, p=winl->txt; j <= count; j++, p++) {    \
  338.         wwif1                        \
  339.         { int n = len;                    \
  340.           char *s1 = inputbuffer;            \
  341.           char *s2 = p;                    \
  342.           while (*s1++ == foldfunc(*s2++))        \
  343.             if (--n == 0) {                \
  344.               wwif2 match++;                \
  345.               break;                    \
  346.             }                        \
  347.         }                        \
  348.       }                            \
  349.     }                            \
  350.   }
  351. #define WWIF1    if (j==0 || (!isalnum(p[-1]) && p[-1]!='_'))
  352. #define WWIF2    if (j==count || !isalnum(*s2) && *s2!='_')
  353.  
  354. void
  355. search(int search_key)
  356. {
  357.   static BOOLEAN foldcase = TRUE;
  358.   static BOOLEAN wholeword = FALSE;
  359.   static char lastsearch[MAX_SEARCH_LEN+1];
  360.   SEARCH_RESULT stbl[SEARCH_RESULT_CACHE];
  361.   int i, sfirst, slast;
  362.   char buf[MAX_SEARCH_LEN];
  363.   char foldbuf[MAX_SEARCH_LEN];
  364.   char matchbuf[30];            /* to sprintf("%ld") into */
  365.   BOOLEAN in_text;
  366.   BOOLEAN has_searched;
  367.   int oldch, ch;
  368.   int firstpos, pos, len;
  369.   long match;
  370.   int memx, line;
  371.  
  372.   if (!editmode) {
  373.     (void)getyx(stdscr, Winy, Winx/*unused*/);
  374.     StickyWinx = Winx = Memx = 0;
  375.     Scrollx = 0;
  376.     set_Winl();
  377.   }
  378.  
  379.   in_text = FALSE;
  380.   has_searched = FALSE;
  381.   pos = len = 0;
  382.   sfirst = slast = 0;
  383.   memx = Memx;
  384.   line = topln+Winy;
  385.   ch = '\0';
  386.   for (;;) {
  387.     match = 0;
  388.     if (len > 0) {
  389.       char flags = 0;
  390.       if (foldcase)  flags |= 0x01;
  391.       if (wholeword) flags |= 0x02;
  392.       for (i = 0; i < len; i++) {
  393.     foldbuf[i] = (char)tolower(((unsigned char*)buf)[i]);
  394.       }
  395.       for (i = sfirst; i != slast; i = (i+1)%SEARCH_RESULT_CACHE) {
  396.     if (stbl[i].flags == flags &&
  397.         stbl[i].len == len &&
  398.         memcmp(stbl[i].string, buf, len) == 0) {
  399.       match = stbl[i].match;
  400.       break;
  401.     }
  402.       }
  403.       if (i == slast) {
  404.     if (foldcase) {
  405.       if (wholeword) {
  406.         SEARCH_DO(WWIF1,WWIF2,(tolower),foldbuf);
  407.       } else {
  408.         SEARCH_DO(;,;,(tolower),foldbuf);
  409.       }
  410.     } else {
  411.       if (wholeword) {
  412.         SEARCH_DO(WWIF1,WWIF2,0+,buf);
  413.       } else {
  414.         SEARCH_DO(;,;,0+,buf);
  415.       }
  416.     }
  417.     stbl[slast].flags = flags;
  418.     stbl[slast].string = memcpy(xmalloc(len), buf, len);
  419.     stbl[slast].len = len;
  420.     stbl[slast].match = match;
  421.     slast = (slast+1) % SEARCH_RESULT_CACHE;
  422.     if (slast == sfirst) {
  423.       free(stbl[sfirst].string);
  424.       sfirst = (sfirst+1) % SEARCH_RESULT_CACHE;
  425.     }
  426.       }
  427.       lastsearch[0] = '\0';
  428.     }
  429.     sprintf(matchbuf, "%ld", match);
  430.     statusline(STATUS_HELP,
  431.            "%c%cearch%c%*s[%s]:%n%-*.*s",
  432.            wholeword ? '\"' : ' ',
  433.              foldcase  ? 's'  : 'S',
  434.            wholeword ? '\"' : ' ',
  435.            len==0? 4 :match<10? 3 :match<100? 2 :match<1000? 1 : 0, "",
  436.            len>0 ? matchbuf : "",
  437.                &firstpos, MAX_SEARCH_LEN, len, buf);
  438.     for (;;) {
  439.       if (in_text) {
  440.     (void)move(Winy, Winx-Scrollx);
  441.       } else {
  442.     (void)move(VIEWLINES, 1+firstpos+pos);
  443.       }
  444.       (void)refresh();
  445.       oldch = ch;
  446.       ch = getch();
  447.       if (KEYABORT(ch)) goto search_done;
  448.       if (KEYRETURN(ch)) ch = search_key;
  449.       switch (ch) {
  450.       case KEYCTRL('L'):
  451.       case KEY_IC:
  452.     goto search_done;
  453.       case KEYCTRL('C'):
  454.     foldcase = !foldcase;
  455.     goto search_again;
  456.       case KEYCTRL('W'):
  457.     wholeword = !wholeword;
  458.     goto search_again;
  459. #ifdef KEY_SHELP
  460.       case KEY_SHELP:
  461. #endif
  462. #ifdef KEY_LHELP
  463.       case KEY_LHELP:
  464. #endif
  465.       case KEY_F(1):
  466.     help(searchmode_help, ARRAYSIZE(searchmode_help));
  467.     break;
  468.       case KEYCTRL('S'):
  469.       case KEYCTRL('R'):
  470.     search_key = ch;
  471.     if (lastsearch[0]) {
  472.       len = pos = strlen(lastsearch);
  473.       memcpy(buf, lastsearch, len);
  474.     } else if (len > 0) {
  475.       BOOLEAN found;
  476.       char *s = foldcase ? foldbuf : buf;
  477.       int x = Memx;
  478.       int y = topln+Winy;
  479.       if (ch == KEYCTRL('R')) {
  480.         static BOOLEAN found_r=FALSE;
  481.         if (!found_r && oldch == ch) {
  482.           x = 0;
  483.           y = nline;
  484.         }
  485.         found=found_r= search_backward(y, x, s, len, foldcase, wholeword);
  486.       } else {
  487.         static BOOLEAN found_s=FALSE;
  488.         if (!found_s && oldch == ch) {
  489.           x = 0;
  490.           y = 0;
  491.         }
  492.         found=found_s= search_forward(y, x, s, len, foldcase, wholeword);
  493.       }
  494.       if (found) {
  495.         in_text = TRUE;
  496.         has_searched = TRUE;
  497.       } else {
  498.         (void)beep();
  499.       }
  500.     }
  501.     goto search_again;
  502.       case '\t':
  503.     in_text = !in_text;
  504.     goto search_again;
  505.       case KEY_BACKSPACE:
  506.       case '\x08':
  507.     if (in_text) goto search_done;
  508.     if (pos > 0) {
  509.       memmove(buf+pos-1, buf+pos, len-pos);
  510.       pos--; len--;
  511.       goto search_again;
  512.     }
  513.     break;
  514.       case KEY_DELETE:
  515.       case KEY_DC:
  516.       case KEYCTRL('D'):
  517.     if (in_text) goto search_done;
  518.     if (pos < len) {
  519.       memmove(buf+pos, buf+pos+1, len-pos-1);
  520.       len--;
  521.       goto search_again;
  522.     }
  523.     break;
  524.       case KEY_LEFT:
  525.       case KEYCTRL('B'):
  526.     if (in_text) goto search_done;
  527.     if (pos > 0) pos--;
  528.     break;
  529.       case KEY_RIGHT:
  530.       case KEYCTRL('F'):
  531.     if (in_text) goto search_done;
  532.     if (pos < len) pos++;
  533.     break;
  534.       case KEY_HOME:
  535.       case KEYCTRL('A'):
  536.     if (in_text) goto search_done;
  537.     pos = 0;
  538.     break;
  539.       case KEY_END:
  540.       case KEYCTRL('E'):
  541.     if (in_text) goto search_done;
  542.     pos = len;
  543.     break;
  544. #ifdef KEY_EOL
  545.       case KEY_EOL:
  546. #endif
  547.       case KEYCTRL('K'):
  548.     if (in_text) goto search_done;
  549.     if (pos < len) {
  550.       len = pos;
  551.       goto search_again;
  552.     }
  553.     break;
  554.       default:
  555.     if (in_text) goto search_done;
  556.     if (PRINTABLE_CHAR(ch) && len < sizeof(buf)) {
  557.       memmove(buf+pos+1, buf+pos, len-pos);
  558.       buf[pos] = (char)ch;
  559.       pos++; len++;
  560.       goto search_again;
  561.     }
  562.       }
  563.     } search_again:;
  564.   } search_done:;
  565.   while (sfirst != slast) {
  566.     free(stbl[sfirst].string);
  567.     sfirst = (sfirst+1) % SEARCH_RESULT_CACHE;
  568.   }
  569.   memcpy(lastsearch, buf, len);
  570.  
  571.   if (ch == KEYCTRL('G')) {
  572.     if (editmode) {
  573.       edit_goto_pos(line, memx);
  574.     } else {
  575.       edit_home();
  576.       goto_curr_diff(FALSE);
  577.     }
  578.   } else if (ch == KEYCTRL('L')) {
  579.     goto_nearest_diff(topln+Winy);
  580.   } else {
  581.     if (ch == KEY_ESCAPE || ch == KEY_IC) {
  582.       if (!has_searched) return;
  583.     } else {
  584.       (void)ungetch(ch);
  585.     }
  586.     if (!editmode) {
  587.       editmode = TRUE;            /* don't reposition the cursor */
  588.       edit_mode();
  589.     }
  590.   }
  591. }
  592.  
  593.  
  594. static BOOLEAN
  595. search_forward(int line, int pos, char *str, int len,
  596.                BOOLEAN fold, BOOLEAN wholeword)
  597. {
  598.   int i, j, count;
  599.   LINE *winl;
  600.   char *p;
  601.  
  602.   for (i=line, winl=linetbl+i; i < nline; i++, winl++, pos=0) {
  603.     if (SYSLINE(winl)) continue;
  604.     count = winl->len-len;
  605.     for (j=pos, p=winl->txt+j; j <= count; j++, p++)
  606.       if (!wholeword || j==0 || (!isalnum(p[-1]) && p[-1]!='_')) {
  607.     int n = len;
  608.     char *s1 = str;
  609.     char *s2 = p;
  610.     while (*s1++ == (fold ? (tolower)(*s2++) : *s2++))
  611.       if (--n == 0)
  612.         if (!wholeword || j==count || !isalnum(*s2) && *s2!='_') {
  613.           edit_goto_pos(i, j+len);
  614.           return TRUE;
  615.             }
  616.       }
  617.   }
  618.   return FALSE;
  619. }
  620.  
  621.  
  622. static BOOLEAN
  623. search_backward(int line, int pos, char *str, int len,
  624.                 BOOLEAN fold, BOOLEAN wholeword)
  625. {
  626.   int i, j, count;
  627.   LINE *winl;
  628.   char *p;
  629.  
  630.   if (pos-- == 0) {
  631.     pos = INT_MAX;
  632.     if (line-- == 0) return FALSE;
  633.   }
  634.   for (i=line, winl=linetbl+i; i >= 0; i--, winl--, pos=INT_MAX) {
  635.     if (SYSLINE(winl)) continue;
  636.     count = winl->len-len;
  637.     if (count > pos) count = pos;
  638.     for (j=count, p=winl->txt+j; j >= 0; j--, p--)
  639.       if (!wholeword || j==0 || (!isalnum(p[-1]) && p[-1]!='_')) {
  640.     int n = len;
  641.     char *s1 = str;
  642.     char *s2 = p;
  643.     while (*s1++ == (fold ? (tolower)(*s2++) : *s2++))
  644.       if (--n == 0)
  645.         if (!wholeword || j==count || !isalnum(*s2) && *s2!='_') {
  646.           edit_goto_pos(i, j);
  647.           return TRUE;
  648.         }
  649.       }
  650.   }
  651.   return FALSE;
  652. }
  653.  
  654.  
  655. /**********************************************************************/
  656.  
  657. static void
  658. set_Winl(void)
  659. {
  660.   Winl = linetbl+topln+Winy;
  661. }
  662.  
  663.  
  664. static void
  665. set_Memx(int memx)
  666. {
  667.   for (Winx=0, Memx=0; Memx < memx; Memx++) {
  668.     if (Winl->txt[Memx] == '\t') {
  669.       Winx += tabwidth-Winx%tabwidth;
  670.     } else {
  671.       Winx++;
  672.     }
  673.   }
  674.   set_Scrollx();
  675. }
  676.  
  677.  
  678. static void
  679. set_valid_Memx(void)
  680. {
  681.   int winx;
  682.   char *p;
  683.  
  684.   if (SYSLINE(Winl)) {
  685.     Memx = 0;
  686.   } else {
  687.     for (winx=0, Memx=0, p=Winl->txt; Memx < Winl->len; Memx++, p++) {
  688.       if (Winx <= winx) break;
  689.       if (*p == '\t') {
  690.     winx += tabwidth-winx%tabwidth;
  691.       } else {
  692.     winx++;
  693.       }
  694.     }
  695.     Winx = winx;
  696.   }
  697.   set_Scrollx();
  698. }
  699.  
  700.  
  701. static void
  702. set_Scrollx(void)
  703. {
  704.   int i;
  705.   int old_scrollx = Scrollx;
  706.  
  707.   if (SYSLINE(Winl)) {
  708.     Winx = 0;
  709.     Scrollx = 0;
  710.   }
  711.   if (Winx >= Scrollx+COLS) {        /* going too far to the right */
  712.     Scrollx = Winx-COLS/2;
  713.   } else if (Winx <= Scrollx) {        /* going too far to the left */
  714.     if (0 < Scrollx) {            /* window is scrolled; scroll back */
  715.       if (Winx < COLS/5*4) {        /* we prefer not to scroll x at all */
  716.     Scrollx = 0;
  717.       } else {                /* too far out; try centering x */
  718.     Scrollx = Winx-COLS/2;
  719.       }
  720.     }
  721.   }
  722.   if (Scrollx == old_scrollx) {        /* no need to scroll window */
  723.     putline(topln+Winy);
  724.   } else {                /* redraw entire window */
  725.     for (i = 0; i < VIEWLINES; i++) {
  726.       putline(topln+i);
  727.     }
  728.   }
  729.   move(Winy, Winx-Scrollx);
  730. }
  731.  
  732.  
  733. static void
  734. update_killed_text(char *add, int len)
  735. {
  736.   if (!kill_append) {
  737.     kill_len = 0;
  738.   }
  739.   if (kill_room < kill_len+len+1) {
  740.     kill_room = kill_len+len+1+KILLINC;
  741.     kill_text = xrealloc(kill_text, kill_room);
  742.   }
  743.   memcpy(kill_text+kill_len, add, len);
  744.   kill_len += len;
  745.   kill_text[kill_len] = '\0';
  746. }
  747.  
  748.  
  749. static void
  750. enlarge_line(LINE *line, int len)
  751. {
  752.   if (line->room < len) {
  753.     line->room = len + ROOMINC;
  754.     line->txt = xrealloc(line->txt, line->room);
  755.   }
  756. }
  757.  
  758.  
  759. void
  760. goto_next_page(void)
  761. {
  762.   int ln;
  763.  
  764.   if (topln == nline-VIEWLINES) {
  765.     edit_bottom();
  766.     return;
  767.   }
  768.   ln = topln+PAGELINES;
  769.   if (nline-VIEWLINES < ln) ln = nline-VIEWLINES;
  770.   if (0 < ln) set_topline(ln);
  771.   if (Winy < SCROLLSIGHT-1) {
  772.     Winy = MIN(nline-1, SCROLLSIGHT-1);
  773.   }
  774.   set_Winl();
  775.   Winx = StickyWinx;
  776.   set_valid_Memx();
  777. }
  778.  
  779.  
  780. void
  781. goto_prev_page(void)
  782. {
  783.   int ln;
  784.  
  785.   if (topln == 0) {
  786.     edit_top();
  787.     return;
  788.   }
  789.   ln = topln-PAGELINES;
  790.   if (ln < 0) ln = 0;
  791.   set_topline(ln);
  792.   if (VIEWLINES-SCROLLSIGHT < Winy) {
  793.     Winy = VIEWLINES-SCROLLSIGHT;
  794.   }
  795.   set_Winl();
  796.   Winx = StickyWinx;
  797.   set_valid_Memx();
  798. }
  799.  
  800.  
  801. static void
  802. edit_top(void)
  803. {
  804.   set_topline(0);
  805.   Winy = 0;
  806.   set_Winl();
  807.   edit_home();
  808. }
  809.  
  810.  
  811. static void
  812. edit_bottom(void)
  813. {
  814.   if (nline <= VIEWLINES) {
  815.     Winy = nline-1;
  816.   } else {
  817.     set_topline(nline-VIEWLINES);
  818.     Winy = VIEWLINES-1;
  819.   }
  820.   set_Winl();
  821.   edit_end();
  822. }
  823.  
  824.  
  825. static void
  826. edit_home(void)
  827. {
  828.   Winx = StickyWinx = 0;
  829.   set_valid_Memx();
  830. }
  831.  
  832.  
  833. static void
  834. edit_end(void)
  835. {
  836.   if (SYSLINE(Winl)) {
  837.     Winx = 0;
  838.   } else {
  839.     Winx = Winl->len*tabwidth;
  840.   }
  841.   set_valid_Memx();
  842.   StickyWinx = Winx;
  843. }
  844.  
  845.  
  846. static void
  847. edit_goto_line(void)
  848. {
  849.   int ch;
  850.   int i, last;
  851.   char buf[200];
  852.   int lineno, ln;
  853.   LINE *line;
  854.  
  855.   ch = ask(gotoline_help, ARRAYSIZE(gotoline_help),
  856.        "12", KEY_ESCAPE, "goto line in file1 or file2?");
  857.   if (ch == KEY_ESCAPE) return;
  858.  
  859.   if (!input(INPUT_NUMERIC, buf, "goto line:")) return;
  860.   lineno = atoi(buf);
  861.   if (lineno == 0) lineno = 1;
  862.  
  863.   last = -1;
  864.   for (i=0, line=linetbl; i < nline; i++, line++) {
  865.     if (line->type == COMMON || line->type == DIFF) {
  866.       last = i;
  867.       ln = ch=='1' ? line->line1 : line->line2;
  868.       if (ln > lineno) break;
  869.       if (ln == lineno) {
  870.     edit_focus_line(i);
  871.     return;
  872.       }
  873.     }
  874.   }
  875.   if (last == -1) {            /* highly improbable */
  876.     message("there are no lines to go to");
  877.   } else {
  878.     edit_focus_line(last);
  879.     message("there is no line %s; going to line %d instead", buf, ln);
  880.   }
  881. }
  882.  
  883.  
  884. static void
  885. edit_goto_pos(int line, int memx)
  886. {
  887.   if (topln+SCROLLSIGHT <= line && line < topln+VIEWLINES-SCROLLSIGHT) {
  888.     Winy = line-topln;
  889.     set_Winl();
  890.   } else {
  891.     edit_focus_line(line);
  892.   }
  893.   set_Memx(memx);
  894.   StickyWinx = Winx;
  895. }
  896.  
  897.  
  898. static void
  899. edit_focus_line(int line)
  900. {
  901.   int ln;
  902.  
  903.   ln = line - LINES/2;
  904.   if (nline-VIEWLINES < ln) ln = nline-VIEWLINES;
  905.   if (ln < 0) ln = 0;
  906.   set_topline(ln);
  907.   Winy = line - topln;
  908.   set_Winl();
  909.   edit_home();
  910. }
  911.  
  912.  
  913. static BOOLEAN
  914. edit_insert_ch(int ch)
  915. {
  916.   if (KEYRETURN(ch)) return edit_insert_str("\n");
  917.   if (SYSLINE(Winl)) return FALSE;
  918.   if (PRINTABLE_CHAR(ch)) {
  919.     char buf[2];
  920.     buf[0] = (char)ch; buf[1] = '\0';
  921.     return edit_insert_str(buf);
  922.   }
  923.   return FALSE;
  924. }
  925.  
  926.  
  927. static BOOLEAN
  928. edit_insert_str(char *text)
  929. {
  930.   LINE *winl;
  931.   int memx, newl;
  932.   char *p, *pend;
  933.   int i;
  934.  
  935.   if (!text || !*text) return FALSE;    /* maybe say "Nothing to yank" */
  936.   for (newl=0, p=text; *p; p++) {
  937.     if (*p == '\n') newl++;
  938.   }
  939.   if (SYSLINE(Winl)) {
  940.     if (text<p && p[-1] != '\n') {    /* must have a terminating \n */
  941.       if (!edit_insert_str("\n") || !edit_up()) return FALSE;
  942.     } else {
  943.       Winl->room = 0;
  944.       Winl->len = 0;
  945.       Winl->txt = NULL;
  946.     }
  947.   }
  948.  
  949.   if (newl == 0) {                      /* text contains no newlines */
  950.     int len = (int)(p-text);
  951.     enlarge_line(Winl, Winl->len+len);
  952.     memmove(Winl->txt+Memx+len, Winl->txt+Memx, Winl->len-Memx);
  953.     memmove(Winl->txt+Memx, text, len);
  954.     Winl->len += len;
  955.     memx = Memx+len;
  956.   } else {
  957.     BOOLEAN incl;
  958.     LINETYPE type;
  959.     int pushlen, inslen;
  960.     int ln = topln+Winy;
  961.     int winy;
  962.  
  963.     if (ln <= diffbeg) diffbeg += newl;
  964.     if (ln <= diffmid) diffmid += newl;
  965.     if (ln <= diffend) diffend += newl;
  966.     if (linetblsiz <= nline+newl) {
  967.       linetblsiz += newl+LINETABNLINC;
  968.       linetbl = xrealloc(linetbl, sizeof(*linetbl)*linetblsiz);
  969.       set_Winl();            /* adjust Winl */
  970.     }
  971.     memmove(Winl+newl+1, Winl+1, (nline-ln-1)*sizeof(*Winl));
  972.     nline += newl;
  973.  
  974.     /*  For type and inclusion of new inserted lines we look at the current
  975.      * line (0), the one above (-1), or use a fixed value:
  976.      *
  977.      *  incl    type    when inserting at a line like
  978.      *  0       0        ...
  979.      *  TRUE    COMMON  vvvvvv
  980.      *  0       0        ...
  981.      *  -1      DIFF    ------
  982.      *  0       0        ...
  983.      *  0       DIFF    ^^^^^^
  984.      *  0       0        ...
  985.      */
  986.     switch (Winl->type) {
  987.     case TOPSEP: incl = TRUE;          type = COMMON; break;
  988.     case MIDSEP: incl = Winl[-1].incl; type = DIFF; break;
  989.     case BOTSEP: incl = Winl[ 0].incl; type = DIFF; break;
  990.     default:     incl = Winl[ 0].incl; type = Winl[0].type; break;
  991.     }
  992.     Winl[newl].incl = Winl->incl;
  993.     Winl[newl].type = Winl->type;
  994.     for (i=0, winl=Winl; i < newl; i++, winl++) {
  995.       winl->incl = incl;
  996.       winl->type = type;
  997.     }
  998.     Winl[newl].line1  = Winl->line1;
  999.     Winl[newl].line2  = Winl->line2;
  1000.     Winl[newl].new    = Winl->new;
  1001.     Winl[newl].broken = Winl->broken;
  1002.     for (i=1, winl=Winl+i; i < newl; i++, winl++) {
  1003.       winl->new = TRUE;
  1004.     }
  1005.     if (SYSLINE(Winl) || Memx == 0) {
  1006.       Winl->new = TRUE;
  1007.       Winl->broken = FALSE;
  1008.     } else if (Memx == Winl->len) {
  1009.       winl->new = TRUE;
  1010.       winl->broken = FALSE;
  1011.     } else {                /* line is broken; mark that */
  1012.       winl->line1 = Winl->line1;
  1013.       winl->line2 = Winl->line2;
  1014.       Winl->new = winl->new = FALSE;
  1015.       Winl->broken = winl->broken = TRUE;
  1016.     }
  1017.  
  1018.     /*  curr text:              insert between B and C:
  1019.      *  AxxxxxBCxxxxxD\0        1xxx2\n
  1020.      *  ExxxF\0                 3xxxxxx4
  1021.      *
  1022.      *  pushlen= len from C to D
  1023.      *  inslen = len from 1 to 2 (excluding \n)
  1024.      */
  1025.     winl = Winl; memx = Memx;
  1026.     p = strchr(text, '\n');             /* we know there's a newline */
  1027.     inslen = (int)(p-text);
  1028.     pushlen = winl->len - memx;
  1029.  
  1030.     do {
  1031.       p++;
  1032.       Winl++;
  1033.       for (pend = p; *pend; pend++) {
  1034.     if (*pend == '\n') break;
  1035.       }
  1036.       Memx = (int)(pend-p);
  1037.       if (!SYSLINE(Winl)) {
  1038.     Winl->room = 0;
  1039.     Winl->len = Memx;
  1040.     Winl->txt = memcpy(xmalloc(Winl->len), p, Winl->len);
  1041.       }
  1042.       p = pend;
  1043.     } while (*p == '\n');
  1044.     if (!SYSLINE(Winl)) {
  1045.       Winl->len = Memx + pushlen;
  1046.       enlarge_line(Winl, Winl->len);
  1047.       memmove(Winl->txt+Memx, winl->txt+memx, pushlen);
  1048.     }
  1049.     winl->len = memx + inslen;
  1050.     enlarge_line(winl, winl->len);
  1051.     memmove(winl->txt+memx, text, inslen);
  1052.     memx = Memx;
  1053.     Memx = 0; Winx = 0;
  1054.  
  1055.     Winy += newl;                       /* add first, ask questions later: */
  1056.     if (PAGELINES < Winy) {
  1057.       int new_topln = topln + Winy - PAGELINES;
  1058.       if (nline-VIEWLINES < new_topln) {
  1059.     new_topln = MAX(0, nline-VIEWLINES);
  1060.       }
  1061.       Winy -= new_topln-topln;
  1062.       set_topline(new_topln);
  1063.     }
  1064.     set_Winl();
  1065.     winy = MAX(0, Winy-newl);
  1066.     move(winy, 0);
  1067.     for (i = winy; i < Winy; i++) (void)insertln();
  1068.     for (i = winy; i < Winy; i++) putline(topln+i);
  1069.   }
  1070.   putline(topln+Winy);
  1071.   set_Memx(memx);
  1072.   StickyWinx = Winx;
  1073.   modified = TRUE;
  1074.   return TRUE;
  1075. }
  1076.  
  1077.  
  1078. static BOOLEAN
  1079. edit_delete(void)
  1080. {
  1081.   int ln = topln+Winy;
  1082.  
  1083.   if (SYSLINE(Winl)) return FALSE;
  1084.   if (Memx < Winl->len) {               /* not standing on the edge */
  1085.     memmove(Winl->txt+Memx, Winl->txt+Memx+1, --Winl->len - Memx);
  1086.     putline(topln+Winy);
  1087.   } else {                              /* del newline */
  1088.     if (ln == nline-1) return FALSE;    /* this is the last line */
  1089.     if (SYSLINE(Winl+1)) {
  1090.       if (Winl->len > 0) return FALSE;  /* can't collapse text+sys line */
  1091.       free(Winl[0].txt);
  1092.       Winl[0] = Winl[1];
  1093.     } else {                            /* collapse with line below */
  1094.       if (Winl[0].new || Winl[0].len == 0) {  /* better to use lower line's */
  1095.     Winl[0].line1 = Winl[1].line1;
  1096.     Winl[0].line2 = Winl[1].line2;
  1097.     Winl[0].new = Winl[1].new;
  1098.     Winl[0].broken = Winl[1].broken;
  1099.       }
  1100.       Winl->len += Winl[1].len;         /* this line will now hold both */
  1101.       enlarge_line(Winl, Winl->len);    /* add some extra space now */
  1102.       memcpy(Winl->txt+Memx, Winl[1].txt, Winl[1].len);
  1103.       free(Winl[1].txt);
  1104.     }
  1105.     memmove(Winl+1, Winl+2, (nline-ln-2)*sizeof(*Winl));
  1106.     if (ln < diffbeg) diffbeg--;
  1107.     if (ln < diffmid) diffmid--;
  1108.     if (ln < diffend) diffend--;
  1109.  
  1110.     (void)deleteln();
  1111.     if (nline-- == topln+VIEWLINES && 0 < topln) { /* pull text above down */
  1112.       set_topline(topln-1);
  1113.       Winy++;
  1114.       set_Winl();
  1115.     } else {
  1116.       putline(topln+VIEWLINES-1);       /* bottom line is empty; redraw it */
  1117.     }
  1118.     putline(ln);                        /* redraw this new, collapsed line */
  1119.   }
  1120.   move(Winy, Winx-Scrollx);             /* (x,y) isn't changed, so go back */
  1121.   StickyWinx = Winx;
  1122.   modified = TRUE;
  1123.   return TRUE;
  1124. }
  1125.  
  1126.  
  1127. static BOOLEAN
  1128. edit_up(void)
  1129. {
  1130.   if (Winy == 0) return FALSE;
  1131.   if (Winy < SCROLLSIGHT && 0 < topln) {
  1132.     set_topline(topln-1);
  1133.   } else {
  1134.     Winy--;
  1135.   }
  1136.   set_Winl();
  1137.   Winx = StickyWinx;
  1138.   set_valid_Memx();
  1139.   return TRUE;
  1140. }
  1141.  
  1142.  
  1143. static BOOLEAN
  1144. edit_down(void)
  1145. {
  1146.   if (VIEWLINES-1 == Winy || nline-1 == Winy) return FALSE;
  1147.   if (PAGELINES <= Winy && topln+VIEWLINES < nline) {
  1148.     set_topline(topln+1);
  1149.   } else {
  1150.     Winy++;
  1151.   }
  1152.   set_Winl();
  1153.   Winx = StickyWinx;
  1154.   set_valid_Memx();
  1155.   return TRUE;
  1156. }
  1157.  
  1158.  
  1159. static BOOLEAN
  1160. edit_left(void)
  1161. {
  1162.   if (Winx == 0) {
  1163.     if (!edit_up()) return FALSE;
  1164.     edit_end();
  1165.     return TRUE;
  1166.   }
  1167.   Winx--;
  1168.   if (Winl->txt[Memx-1] == '\t') {
  1169.     int winx, prevwinx;
  1170.     char *p;
  1171.     for (winx=prevwinx=0, p=Winl->txt; winx <= Winx; p++) {
  1172.       prevwinx = winx;
  1173.       if (*p == '\t') {
  1174.     winx += tabwidth-winx%tabwidth;
  1175.       } else {
  1176.     winx++;
  1177.       }
  1178.     }
  1179.     Winx = prevwinx;
  1180.   }
  1181.   set_valid_Memx();
  1182.   StickyWinx = Winx;
  1183.   return TRUE;
  1184. }
  1185.  
  1186.  
  1187. static BOOLEAN
  1188. edit_right(void)
  1189. {
  1190.   if (!SYSLINE(Winl) && Memx < Winl->len) {
  1191.     Winx++;
  1192.   } else {
  1193.     if (!edit_down()) return FALSE;
  1194.     edit_home();
  1195.   }
  1196.   set_valid_Memx();
  1197.   StickyWinx = Winx;
  1198.   return TRUE;
  1199. }
  1200.